﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Globalization;
using ExpressionEvaluator.BuilderTreeNode;
using ExpressionEvaluator.Expceptions;
using ExpressionEvaluator.Extended;

namespace ExpressionEvaluator
{
	/// <summary>
	/// Expression tree builder.
	/// </summary>
	internal class ExpressionTreeBuilder
	{
		/// <summary>
		/// List of supported operators.
		/// </summary>
		private static List<ExpressionOperator> _OperatorList;

		private const string cExecutableOrDecimalDelimiter = ".";
		private const string cFunctionDelimiter = ",";
        private const string cCurlyBracketStart = "{";
        private const string cCurlyBracketEnd = "}";
		private const string cBracketStart = "(";
		private const string cBracketEnd = ")";
		private const string cIndexStart = "[";
		private const string cIndexEnd = "]";
		private const string cNewInstance = "new";
		private const string cNotOperator = "!";
        private const string cNotExecutableOperator = "Not";
		private const string cLambda = "=>";
		public const string cMinusSign = "-";

		/// <summary>
		/// Initializes the <see cref="ExpressionTreeBuilder"/> class.
		/// </summary>
		static ExpressionTreeBuilder()
		{
			_OperatorList = new List<ExpressionOperator>();
			CreateOperators();
		}

		/// <summary>
		/// Creates the operators list.
		/// </summary>
		private static void CreateOperators()
		{
			// add lambda operator hack
			_OperatorList.Add(new ExpressionOperatorLambda());

			const int Multiplicative = 1;
			const int Additive = 2;
			const int Relational = 4;
			const int Equality = 5;
			// Multiplicative operators
			_OperatorList.Add(
				new ExpressionOperator(Multiplicative, "*", (x, y) => Expression.Multiply(x, y)));
			_OperatorList.Add(
				new ExpressionOperator(Multiplicative, "/", (x, y) => Expression.Divide(x, y)));
			_OperatorList.Add(
				new ExpressionOperator(Multiplicative, "%", (x, y) => Expression.Modulo(x, y)));
			// Additive operators
			_OperatorList.Add(
				new ExpressionOperator(Additive, "+", (x, y) =>
				{
					if (x.Type == typeof(string))
					{
						return Expression.Add(x, y, 
							typeof(String).GetMethod("Concat", new Type[] {typeof(string), typeof(string)}));
					}
					return Expression.Add(x, y);
				}));
			_OperatorList.Add(
				new ExpressionOperator(Additive, "-", (x, y) => Expression.Subtract(x, y)));
			// Relational and type testing operators
			_OperatorList.Add(
				new ExpressionOperator(Relational, "<", (x, y) => Expression.LessThan(x, y)));
			_OperatorList.Add(
				new ExpressionOperator(Relational, ">", (x, y) => Expression.GreaterThan(x, y)));
			_OperatorList.Add(
				new ExpressionOperator(Relational, "<=", (x, y) => Expression.LessThanOrEqual(x, y)));
			_OperatorList.Add(
				new ExpressionOperator(Relational, ">=", (x, y) => Expression.GreaterThanOrEqual(x, y)));
			// Equality operators
			_OperatorList.Add(
				new ExpressionOperator(Equality, "==", (x, y) => Expression.Equal(x, y)));
			_OperatorList.Add(
				new ExpressionOperator(Equality, "!=", (x, y) => Expression.NotEqual(x, y)));
			// use assign as equal because of lambda.ToString() strange behaviour
			_OperatorList.Add(
				new ExpressionOperator(Equality, "=", (x, y) => Expression.Equal(x, y)));
			// logical and
			_OperatorList.Add(
				new ExpressionOperator(6, "&", (x, y) => Expression.And(x, y)));
			// logical xor
			_OperatorList.Add(
				new ExpressionOperator(7, "^", (x, y) => Expression.ExclusiveOr(x, y)));
			// logical or
			_OperatorList.Add(
				new ExpressionOperator(8, "|", (x, y) => Expression.Or(x, y)));
			// conditional and
			_OperatorList.Add(
				new ExpressionOperator(9, "&&", (x, y) => Expression.AndAlso(x, y)));
			// conditional or
			_OperatorList.Add(
				new ExpressionOperator(10, "||", (x, y) => Expression.OrElse(x, y)));
		}

		/// <summary>
		/// Builds the tree.
		/// </summary>
		/// <param name="codeBlocks">The code blocks.</param>
		/// <param name="lookupNamespaces">The lookup namespaces.</param>
		/// <param name="lookupAssemblies">The lookup assemblies.</param>
		/// <param name="parameters">The parameters.</param>
		/// <returns></returns>
		internal static Expression BuildTree(List<CodeBlock> codeBlocks, List<string> lookupNamespaces, List<string> lookupAssemblies, List<ExtensionInfo> extensions, params ParameterExpression[] parameters)
		{
			// create tree builder structure
			BuilderTree tree = new BuilderTree(lookupNamespaces, lookupAssemblies, extensions, parameters);
			// initialize parser state (state machine)
			ParserState parserState = ParserState.None;
			int codeLength = codeBlocks.Count;
			// iterate through codeblocks
			for (int i = 0; i < codeLength; i++)
			{
				CodeBlock currentCodeBlock = codeBlocks[i];
				// use state machine alghorithm to create builder tree structure
				switch (currentCodeBlock.CodeType)
				{
					// if current element is text type
					case CodeTypeEnum.Text:
						parserState = ProcessText(tree, parserState, currentCodeBlock);
						break;
					// if current element is executable type
					case CodeTypeEnum.Executable:
						parserState = ParseExecutable(tree, parserState, currentCodeBlock);
						break;
					// if current element is operation type
					case CodeTypeEnum.Operation:
						parserState = ProcessOperator(tree, parserState, currentCodeBlock);
						break;
					// if current element is delimiter type
					case CodeTypeEnum.Delimiter:
						// switch based on current code block text
						switch (currentCodeBlock.Text)
						{
                            // curly bracket start '{'
                            case cCurlyBracketStart:
                                tree.Current.Idents.Add(new CodeBlock(string.Empty, CodeTypeEnum.Executable, currentCodeBlock.StartIndex, currentCodeBlock.Lenght));
                                goto case cBracketStart;
							// bracket start '('
							case cBracketStart:
								parserState = ProcessBracketStart(tree, parserState, currentCodeBlock);
								break;
							// bracket end ')' or '}'
                            case cCurlyBracketEnd:
							case cBracketEnd:
								parserState = ProcessEndBracket(tree, parserState, currentCodeBlock);
								break;
							// delimiter '.'
							case cExecutableOrDecimalDelimiter:
								parserState = ProcessExecutableOrDecimalDelimiter(tree, parserState, currentCodeBlock);
								break;
							// delimiter ','
							case cFunctionDelimiter:
								parserState = ProcessFunctionDelimiter(tree, parserState, currentCodeBlock);
								break;
							// index start '['
							case cIndexStart:
								parserState = ProcessIndexStart(tree, parserState, currentCodeBlock);
								break;
							// index end ']'
							case cIndexEnd:
								parserState = ProcessIndexEnd(tree, parserState, currentCodeBlock);
								break;
							default:
								throw new SyntaxException("Syntax error!", parserState, currentCodeBlock);
						}
						break;
				}
			}

			return tree.GenerateExpression();
		}

		/// <summary>
		/// Processes the index end.
		/// </summary>
		/// <param name="tree">The tree.</param>
		/// <param name="parserState">State of the parser.</param>
		/// <param name="currentCodeBlock">The current code block.</param>
		/// <returns>New parser state.</returns>
		private static ParserState ProcessIndexEnd(BuilderTree tree, ParserState parserState, CodeBlock currentCodeBlock)
		{
			switch (parserState)
			{
				case ParserState.Text:
				case ParserState.BracketEnd:
				case ParserState.IndexerEnd:
				case ParserState.Executable:
					// try to find nearest index bracket
					if (tree.FindNearestClosingIndexBracketAndSetCurrent())
					{
						(tree.Current as IndexerNode).EndBracket = currentCodeBlock;
					}
					else
					{
						throw new SyntaxException("Syntax error!", parserState, currentCodeBlock);
					}
					parserState = ParserState.IndexerEnd;
					break;
				case ParserState.BracketStart:
				default:
					throw new SyntaxException("Syntax error!", parserState, currentCodeBlock);
			}
			return parserState;
		}

		/// <summary>
		/// Processes the index start.
		/// </summary>
		/// <param name="tree">The tree.</param>
		/// <param name="parserState">State of the parser.</param>
		/// <param name="currentCodeBlock">The current code block.</param>
		/// <returns>New parser state.</returns>
		private static ParserState ProcessIndexStart(BuilderTree tree, ParserState parserState, CodeBlock currentCodeBlock)
		{
			switch (parserState)
			{
				case ParserState.BracketEnd:
				case ParserState.IndexerEnd:
				case ParserState.Executable:
					// create new indexer node
					IndexerNode newIndexerNode = new IndexerNode();
					newIndexerNode.StartBracket = currentCodeBlock;
					tree.AddNewAndSetToCurrent(newIndexerNode);
					break;
				default:
					throw new SyntaxException("Syntax error!", parserState, currentCodeBlock);
			}
			parserState = ParserState.IndexerStart;
			return parserState;
		}

		/// <summary>
		/// Processes the function delimiter.
		/// </summary>
		/// <param name="tree">The tree.</param>
		/// <param name="parserState">State of the parser.</param>
		/// <param name="currentCodeBlock">The current code block.</param>
		/// <returns></returns>
		private static ParserState ProcessFunctionDelimiter(BuilderTree tree, ParserState parserState, CodeBlock currentCodeBlock)
		{
			switch (parserState)
			{
				case ParserState.Text:
				case ParserState.Executable:
				case ParserState.BracketEnd:
					// just go back to parent expression
					tree.FindNearestClosingBracketAndSetCurrent();
					break;
				default:
					throw new SyntaxException("Syntax error!", parserState, currentCodeBlock);
			}
			parserState = ParserState.FunctionDelimiter;
			return parserState;
		}

		/// <summary>
		/// Processes the executable or decimal delimiter.
		/// </summary>
		/// <param name="tree">The tree.</param>
		/// <param name="parserState">State of the parser.</param>
		/// <param name="currentCodeBlock">The current code block.</param>
		/// <returns>New parser state.</returns>
		private static ParserState ProcessExecutableOrDecimalDelimiter(BuilderTree tree, ParserState parserState, CodeBlock currentCodeBlock)
		{
			switch (parserState)
			{
				case ParserState.IndexerEnd:
				case ParserState.BracketEnd:
					// if current is new instance go back
					if (!(tree.Current is NewInstanceNode))
					{
						tree.GoToParent();
					}
					// else add to idents
					(tree.Current as ExpressionNode).Idents.Add(currentCodeBlock);
					parserState = ParserState.Delimiter;
					break;
				case ParserState.Executable:
					// if current node already contains nodes, then every next executable is property or function
					if (tree.Current.Nodes.Count > 0)
					{
						FunctionOrPropertyNode newPropertyNode = new FunctionOrPropertyNode();
						(tree.Current as ExpressionNode).MoveLastIdentToFunction(newPropertyNode);
						// by default predict property
						newPropertyNode.IsProperty = true;
						tree.Current.Idents.Add(currentCodeBlock);
						tree.AddNewAndSetToCurrent(newPropertyNode);
						tree.GoToParent();
					}
					// else just add to identify executable delimiters
					else
					{
						(tree.Current as ExpressionNode).Idents.Add(currentCodeBlock);
					}
					parserState = ParserState.Delimiter;
					break;
				default:
					throw new SyntaxException("Syntax error!", parserState, currentCodeBlock);
			}
			return parserState;
		}

		/// <summary>
		/// Processes the end bracket.
		/// </summary>
		/// <param name="tree">The tree.</param>
		/// <param name="parserState">State of the parser.</param>
		/// <param name="currentCodeBlock">The current code block.</param>
		/// <returns>New parser state.</returns>
		private static ParserState ProcessEndBracket(BuilderTree tree, ParserState parserState, CodeBlock currentCodeBlock)
		{
			switch (parserState)
			{
				case ParserState.Text:
				case ParserState.BracketEnd:
				case ParserState.IndexerEnd:
				case ParserState.Executable:
					// find nearest closing bracket and set end bracket codeblock
					if (tree.FindNearestClosingBracketAndSetCurrent())
					{
						if (tree.Current is BracketNode)
						{
							(tree.Current as BracketNode).EndBracket = currentCodeBlock;
						}
						else if (tree.Current is NewInstanceNode)
						{
							(tree.Current as NewInstanceNode).EndBracket = currentCodeBlock;
						}
					}
					else
					{
						throw new SyntaxException("Closing tag not found!", parserState, currentCodeBlock);
					}
					parserState = ParserState.BracketEnd;
					break;
				case ParserState.BracketStart:
					// function node allows empty bracket
					if (tree.Current is FunctionOrPropertyNode)
					{
						parserState = ParserState.BracketEnd;
					}
					// new instance node allows empty bracket
					else if (tree.Current is NewInstanceNode)
					{
						(tree.Current as NewInstanceNode).EndBracket = currentCodeBlock;
						parserState = ParserState.BracketEnd;
					}
					else
						throw new SyntaxException("Empty brackets!", parserState, currentCodeBlock);
					break;
				default:
					throw new SyntaxException("Syntax error!", parserState, currentCodeBlock);
			}
			return parserState;
		}



		/// <summary>
		/// Processes the bracket start.
		/// </summary>
		/// <param name="tree">The tree.</param>
		/// <param name="parserState">State of the parser.</param>
		/// <param name="currentCodeBlock">The current code block.</param>
		/// <returns>New parser state.</returns>
		private static ParserState ProcessBracketStart(BuilderTree tree, ParserState parserState, CodeBlock currentCodeBlock)
		{
			switch (parserState)
			{
				case ParserState.Negator:
					NotNode notNode = new NotNode();
					tree.AddNewAndSetToCurrent(notNode);
					ExpressionNode notInnerNode = new ExpressionNode();
					tree.AddNewAndSetToCurrent(notInnerNode);
					break;
				// if two bracket side by side, then delimite with expression node 
				case ParserState.BracketStart:
					// create new expression node only if current is bracket
					if (tree.Current is BracketNode)
					{
						ExpressionNode bracketContentNode = new ExpressionNode();
						tree.AddNewAndSetToCurrent(bracketContentNode);
					}
					goto case ParserState.Operator;
				case ParserState.IndexerStart:
				case ParserState.None:
				case ParserState.Operator:
				case ParserState.FunctionDelimiter:
					// add new bracket node
					BracketNode newNode = new BracketNode();
					newNode.StartBracket = currentCodeBlock;
					tree.AddNewAndSetToCurrent(newNode);
					break;
				case ParserState.Executable:
					// if current block is instance and stil don't have any node inside
					if ((tree.Current is NewInstanceNode)
						&& ((tree.Current as NewInstanceNode).EndBracket == null))
					{
						(tree.Current as NewInstanceNode).StartBracket = currentCodeBlock;
					}
					// else new function should be created
					else
					{
						FunctionOrPropertyNode newfunctionNode = new FunctionOrPropertyNode();
						// move last ident in current section to this section (examble class.function => class -> function
						if (!(tree.Current as ExpressionNode).MoveLastIdentToFunction(newfunctionNode))
						{
							throw new SyntaxException("Function cannot be used without type or parameter!", parserState, currentCodeBlock);
						}
						tree.AddNewAndSetToCurrent(newfunctionNode);
					}
					break;
				default:
					throw new SyntaxException("Syntax error!", parserState, currentCodeBlock);
			}
			return ParserState.BracketStart;
		}

		/// <summary>
		/// Processes the operator.
		/// </summary>
		/// <param name="tree">The tree.</param>
		/// <param name="parserState">State of the parser.</param>
		/// <param name="currentCodeBlock">The current code block.</param>
		/// <returns>New parser state.</returns>
		private static ParserState ProcessOperator(BuilderTree tree, ParserState parserState, CodeBlock currentCodeBlock)
		{
			// check for minus sign state
			if (currentCodeBlock.Text == cMinusSign)
			{
				switch (parserState)
				{
					case ParserState.None:
					case ParserState.BracketStart:
					case ParserState.FunctionDelimiter:
					case ParserState.Operator:
						ExpressionNode executableNode = new ExpressionNode();
						tree.AddNewAndSetToCurrent(executableNode);
						tree.Current.Idents.Add(currentCodeBlock);
						return ParserState.MinusSign;
					default:
						// by default do nothing and process minus sign as normal operator
						break;
				}
			}

			if (currentCodeBlock.Text == cNotOperator)
			{
				switch (parserState)
				{
					case ParserState.None:
					case ParserState.BracketStart:
					case ParserState.FunctionDelimiter:
					case ParserState.Operator:
						return ParserState.Negator;
					default:
						throw new SyntaxException("Syntax error!", parserState, currentCodeBlock);
				}
			}
			else
			{
				// find operator in operator list
				ExpressionOperator op = _OperatorList.Find(item => item.OperatorName == currentCodeBlock.Text);
				switch (parserState)
				{
					case ParserState.Text:
					case ParserState.BracketEnd:
					case ParserState.IndexerEnd:
					case ParserState.Executable:
						// go back to parent expression
						tree.GoToParentExpression();
						ExpressionNode currentExpression = tree.Current as ExpressionNode;
						// if expression in function, create new node
						if ((currentExpression == null) || (currentExpression is NewInstanceNode))
						{
							ExpressionNode newFunctionOperatorNode = new ExpressionNode();
							tree.Current.MoveLastToNewNode(newFunctionOperatorNode);
							tree.AddNewAndSetToCurrent(newFunctionOperatorNode);
							currentExpression = newFunctionOperatorNode;
						}
						// if we already have operator, then we need to reorder tree
						if (currentExpression.Operator != null)
						{
							// if new operator priority is same or greater, then create new node and set as parent to current
							if (op.Priority >= currentExpression.Operator.Priority)
							{
								ExpressionNode newParentNode = new ExpressionNode();
								tree.SetAsParentToCurrent(newParentNode);
							}
							// else move last node as first node to new expression node
							else
							{
								ExpressionNode newNode = new ExpressionNode();
								currentExpression.MoveLastToNewNode(newNode);
								tree.AddNewAndSetToCurrent(newNode);
							}
						}
						// set operator
						(tree.Current as ExpressionNode).SetOperator(op, currentCodeBlock);
						break;
					default:
						throw new SyntaxException("Syntax error!", parserState, currentCodeBlock);
				}
				return ParserState.Operator;
			}
		}

		/// <summary>
		/// Parses the executable.
		/// </summary>
		/// <param name="tree">The tree.</param>
		/// <param name="parserState">State of the parser.</param>
		/// <param name="currentCodeBlock">The current code block.</param>
		/// <returns>New parser state.</returns>
		private static ParserState ParseExecutable(BuilderTree tree, ParserState parserState, CodeBlock currentCodeBlock)
		{
            // process not operator from linq query
            if (currentCodeBlock.Text == cNotExecutableOperator)
            {
                switch (parserState)
                {
                    case ParserState.None:
                    case ParserState.BracketStart:
                    case ParserState.FunctionDelimiter:
                    case ParserState.Operator:
                        return ParserState.Negator;
                    default:
                        throw new SyntaxException("Syntax error!", parserState, currentCodeBlock);
                }
            }

			switch (parserState)
			{
				// if executable in bracket and current node is bracket, then add additional expression node
				case ParserState.BracketStart:
					// create new expression node only if current is bracket
					if (tree.Current is BracketNode)
					{
						ExpressionNode bracketContentNode = new ExpressionNode();
						tree.AddNewAndSetToCurrent(bracketContentNode);
					}
					goto case ParserState.None;
				// process create new expression node
				case ParserState.None:
				case ParserState.FunctionDelimiter:
				case ParserState.IndexerStart:
				case ParserState.Operator:
					// if new keyword used then new instance node should be used
					if (currentCodeBlock.Text == cNewInstance)
					{
						NewInstanceNode instanceCreator = new NewInstanceNode();
						tree.AddNewAndSetToCurrent(instanceCreator);
						parserState = ParserState.NewInstance;
						break;
					}
					// else add new normal executable node
					else
					{
						ExpressionNode executableNode = new ExpressionNode();
						tree.AddNewAndSetToCurrent(executableNode);
						goto case ParserState.Delimiter;
					}
				// two executables only after new keyword
				case ParserState.Executable:
					if (!(tree.Current is NewInstanceNode))
					{
						throw new SyntaxException("Two not delimited executables side by side!", parserState, currentCodeBlock);
					}
					goto case ParserState.Delimiter;
				// add current codeblock to identify type, field, property, function of expression
				case ParserState.NewInstance:
				case ParserState.Delimiter:
				case ParserState.MinusSign:
					tree.Current.Idents.Add(currentCodeBlock);
					break;
				default:
					throw new SyntaxException("Syntax error!", parserState, currentCodeBlock);
			}
			return ParserState.Executable;
		}

		/// <summary>
		/// Processes the text.
		/// </summary>
		/// <param name="tree">The tree.</param>
		/// <param name="parserState">State of the parser.</param>
		/// <param name="currentCodeBlock">The current code block.</param>
		/// <returns>New parser state.</returns>
		private static ParserState ProcessText(BuilderTree tree, ParserState parserState, CodeBlock currentCodeBlock)
		{
			switch (parserState)
			{
				case ParserState.None:
				case ParserState.BracketStart:
				case ParserState.FunctionDelimiter:
				case ParserState.IndexerStart:
				case ParserState.Operator:
					// create text node and add current code block as text
					TextNode textNode = new TextNode();
					tree.AddNewAndSetToCurrent(textNode);
					tree.Current.Idents.Add(currentCodeBlock);
					break;
				default:
					throw new SyntaxException("Syntax error!", parserState, currentCodeBlock);
			}
			return ParserState.Text;
		}

	}
}
